package com.kapcb.schedule.register;

import com.kapcb.schedule.annotation.AutoXxlJob;
import com.kapcb.schedule.bean.XxlJobGroup;
import com.kapcb.schedule.bean.XxlJobInfo;
import com.kapcb.schedule.client.XxlJobAdminHttpClient;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * <a>Title: XxlJobAutoRegister </a>
 * <a>Author: kapcb <a>
 * <a>Description: XxlJobAutoRegister <a>
 * 核心类实现 ApplicationListener 接口，在接收到 ApplicationReadyEvent 事件后开始执行自动注册逻辑
 *
 * @author Kpacb
 * @version 1.0
 * @date 2024/5/29 21:45
 * @since 1.0
 */
@Slf4j
public class XxlJobAutoRegister implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private final XxlJobAdminHttpClient xxlJobAdminHttpClient;

    public XxlJobAutoRegister(XxlJobAdminHttpClient xxlJobAdminHttpClient) {
        this.xxlJobAdminHttpClient = xxlJobAdminHttpClient;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 注册执行器
        registerJobGroup();
        addJobInfo();//注册任务

    }

    /**
     * 自动注册执行器的代码非常简单，根据配置文件中的appName和title精确匹配查看调度中心是否已有执行器被注册过了，如果存在则跳过，不存在则新注册一个
     */
    private void registerJobGroup() {
        if (xxlJobAdminHttpClient.preciselyCheck()) {
            return;
        }
        if (xxlJobAdminHttpClient.registerGroup()) {
            log.info("auto register xxl-job group success!");
        }
    }

    /**
     * 1、通过applicationContext拿到spring容器中的所有bean，再拿到这些bean中所有添加了@XxlJob注解的方法
     * 2、对上面获取到的方法进行检查，是否添加了我们自定义的@XxlRegister注解，如果没有则跳过，不进行自动注册
     * 3、对同时添加了@XxlJob和@XxlRegister的方法，通过执行器id和jobHandler的值判断是否已经在调度中心注册过了，如果已存在则跳过
     * 4、对于满足注解条件且没有注册过的jobHandler，调用接口注册到调度中心
     */
    private void addJobInfo() {
        List<XxlJobGroup> jobGroups = xxlJobAdminHttpClient.getJobGroup();
        XxlJobGroup xxlJobGroup = jobGroups.get(0);

        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = applicationContext.getBean(beanDefinitionName);

            Map<Method, XxlJob> annotatedMethods = MethodIntrospector.
                    selectMethods(bean.getClass(), (MethodIntrospector.MetadataLookup<XxlJob>) method -> AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class));

            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                Method executeMethod = methodXxlJobEntry.getKey();
                XxlJob xxlJob = methodXxlJobEntry.getValue();

                // 自动注册
                if (executeMethod.isAnnotationPresent(AutoXxlJob.class)) {
                    AutoXxlJob xxlRegister = executeMethod.getAnnotation(AutoXxlJob.class);
                    List<XxlJobInfo> jobInfo = xxlJobAdminHttpClient.getJobInfo(xxlJobGroup.getId(), xxlJob.value());
                    if (!jobInfo.isEmpty()) {
                        // 因为是模糊查询，需要再判断一次
                        Optional<XxlJobInfo> first = jobInfo.stream()
                                .filter(xxlJobInfo -> xxlJobInfo.getExecutorHandler().equals(xxlJob.value()))
                                .findFirst();
                        if (first.isPresent())
                            continue;
                    }

                    XxlJobInfo xxlJobInfo = new XxlJobInfo(xxlJobGroup, xxlJob, xxlRegister);
                    Integer jobInfoId = xxlJobAdminHttpClient.addJobInfo(xxlJobInfo);
                }
            }
        }
    }

}
